/*
 *  Copyright 2011 Iurii Dunko, Iana Potochniak.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *  under the License.
 */

package org.opu.yyminijava.visitor;

import java.util.ArrayList;
import java.util.List;
import org.opu.yyminijava.symboltable.ClassRecord;
import org.opu.yyminijava.symboltable.MethodRecord;
import org.opu.yyminijava.symboltable.SymbolTable;
import org.opu.yyminijava.symboltable.VarRecord;
import org.opu.yyminijava.syntaxtree.ClassDeclaration;
import org.opu.yyminijava.syntaxtree.ClassType;
import org.opu.yyminijava.syntaxtree.CompilationUnit;
import org.opu.yyminijava.syntaxtree.FieldDeclaration;
import org.opu.yyminijava.syntaxtree.FormalParameter;
import org.opu.yyminijava.syntaxtree.FormalParameterList;
import org.opu.yyminijava.syntaxtree.LocalVariableDeclaration;
import org.opu.yyminijava.syntaxtree.MethodDeclaration;
import org.opu.yyminijava.syntaxtree.MethodDeclarator;
import org.opu.yyminijava.syntaxtree.MethodHeader;
import org.opu.yyminijava.syntaxtree.Name;
import org.opu.yyminijava.syntaxtree.Rule;
import org.opu.yyminijava.syntaxtree.TypeDeclaration;
import org.opu.yyminijava.syntaxtree.VariableDeclarator;

/**
 *
 * @author Iurii Dunko
 * @author Iana  Potochniak
 */
public class SymbolTableBuildHandler implements VisitorHandler{

    private SymbolTable symbolTable;
    private boolean putValueWhenFound;

    public SymbolTableBuildHandler(SymbolTable symbolTable,
            boolean putValueWhenFound) {

        this.symbolTable = symbolTable;
        this.putValueWhenFound = putValueWhenFound;
    }

    public SymbolTableBuildHandler(SymbolTable symbolTable) {
        this(symbolTable, true);
    }

    @Override
    public void onVisitStart(Rule rule, boolean isAbstractRule) {
        // build table
        if(rule instanceof ClassDeclaration){
            ClassDeclaration cd = (ClassDeclaration) rule;
            String n = cd.getName();
            ClassRecord cr = new ClassRecord(n, new ClassType(new Name(n)), cd);
            if (putValueWhenFound) {
                symbolTable.put(cr);
            }
            symbolTable.enterScope();
        } else if (rule instanceof FieldDeclaration){
            FieldDeclaration fd = (FieldDeclaration) rule;
            for(String name: fd.getIdentifierList().getNames()){
                VarRecord vr = new VarRecord(name, fd.getType(), null);
                putVar(vr);
            }
        } else if (rule instanceof MethodDeclaration){
            MethodDeclaration md = (MethodDeclaration) rule;
            MethodHeader h = md.getHeader();
            MethodDeclarator d = h.getDeclarator();
            MethodRecord mr = new MethodRecord(d.getSimpleName(), h.getType(), parameters(md));

            if (putValueWhenFound) {
                symbolTable.put(mr);
            }

            symbolTable.enterScope();

            FormalParameterList pl = d.getParameterList();

            if(pl != null){
                for (FormalParameter p : pl.getParameters()) {
                    putVar(new VarRecord(p.getSimpleName(), p.getType(), null));
                }
            }
        } else if (rule instanceof LocalVariableDeclaration){
            LocalVariableDeclaration lvd = (LocalVariableDeclaration) rule;
            List<VariableDeclarator> vd = lvd.getVariableDeclarators().getVariables();

            for (VariableDeclarator v : vd) {
                VarRecord vr = new VarRecord(v.getSimpleName(), lvd.getType(), null);
                putVar(vr);
            }
        }
    }

    private void putVar(VarRecord vr) {
        if (putValueWhenFound) {
            symbolTable.put(vr);
        }
    }

    @Override
    public void onVisitEnd(Rule rule, boolean isAbstractRule) {
        if(rule instanceof ClassDeclaration){
            symbolTable.exitScope();
        } else if (rule instanceof MethodDeclaration){
            symbolTable.exitScope();
        } else if (rule instanceof CompilationUnit){
            CompilationUnit cu = (CompilationUnit) rule;
            List<TypeDeclaration> td = cu.getTypeDeclarations();

            for (TypeDeclaration d : td) {
                ClassDeclaration cd = d.getClassDeclaration();
                ClassType name = cd.getSuperName();

                if (name != null){
                    ClassRecord classRecord = new ClassRecord(name.getName());
                    classRecord = (ClassRecord) symbolTable.lookup(classRecord);
                    cd.setSuperClass(classRecord.getValue());

                    symbolTable.addDefaultScope(cd.getName(), name.getName());
                }
            }

        }
    }

    private List<FormalParameter> parameters(MethodDeclaration value) {
        FormalParameterList pl = value.getHeader().getDeclarator().getParameterList();
        if (pl != null) {
            return pl.getParameters();
        } else {
            return new ArrayList<FormalParameter>();
        }
    }


}
